<!doctype html>
<html lang="zh-cn">
<head>

    <meta charset="utf-8">
    <meta name="generator" content="Hugo 0.57.2" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Ch05 使用流 | The Sky of OtsWang</title>
    <meta property="og:title" content="Ch05 使用流 - The Sky of OtsWang">
    <meta property="og:type" content="article">
        
    <meta property="article:published_time" content="2019-04-26T16:49:28&#43;08:00">
        
        
    <meta property="article:modified_time" content="2019-04-26T16:49:28&#43;08:00">
        
    <meta name="Keywords" content="golang,go语言,otswang,java,博客,python">
    <meta name="description" content="Ch05 使用流">
        
    <meta name="author" content="OtsWang">
    <meta property="og:url" content="https://otswang.gitee.io/hugo/post/java/java_8_inaction/ch05/">
    <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">

    <link rel="stylesheet" href="/hugo/css/normalize.css">
    
        <link rel="stylesheet" href="/hugo/css/prism.css">
    
    <link rel="stylesheet" href="/hugo/css/style.css">
    <script type="text/javascript" src="//cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>

    


    
    
</head>

<body>
<header id="header" class="clearfix">
    <div class="container">
        <div class="col-group">
            <div class="site-name ">
                
                    <a id="logo" href="https://otswang.gitee.io/hugo/">
                        The Sky of OtsWang
                    </a>
                
                <p class="description">擅长写HelloWorld的小小码农</p>
            </div>
            <div>
                <nav id="nav-menu" class="clearfix">
                    
                    
                    <a  href="https://otswang.gitee.io/hugo/" title="Home">Home</a>
                    
                    <a  href="https://otswang.gitee.io/hugo/tags/" title="Tags">Tags</a>
                    
                    <a  href="https://otswang.gitee.io/hugo/categories/" title="Categories">Categories</a>
                    
                    <a  href="https://otswang.gitee.io/hugo/archives/" title="Archives">Archives</a>
                    
                    <a  href="https://otswang.gitee.io/hugo/about/" title="About">About</a>
                    
                </nav>
            </div>
        </div>
    </div>
</header>


<div id="body">
    <div class="container">
        <div class="col-group">

            <div class="col-8" id="main">
                <div class="res-cons">
                    <article class="post">
                        <header>
                            <h1 class="post-title">Ch05 使用流</h1>
                        </header>
                        <date class="post-meta meta-date">
                            2019年4月26日
                        </date>
                        
                        <div class="post-meta">
                            <span>|</span>
                            
                                <span class="meta-category"><a href="https://otswang.gitee.io/hugo/categories/java-java8inaction">Java-Java8InAction</a></span>
                            
                        </div>
                        
                        
                        
                        <div class="post-content">
                            <p>Stream API 支持的复杂操作：筛选、切片、映射、查找、匹配和归约 。 Stream 中的特殊的流：数值流、来
自文件和数组等多种来源的流，以及无限流。</p>

<h1 id="映射-map-与-flatmap">映射：map 与 flatMap</h1>

<p>一个非常常见的数据处理套路就是从某些对象中选择信息。比如在SQL里，你可以从表中选择一列。Stream API也通过 map 和 flatMap 方法提供了类似的工具。</p>

<p><strong>一个例子</strong></p>

<p>(1) 给定一个数字列表，如何返回一个由每个数的平方构成的列表呢？例如，给定[1, 2, 3, 4,5]，应该返回[1, 4, 9, 16, 25]。</p>

<p>答案：你可以利用 map 方法的Lambda，接受一个数字，并返回该数字平方的Lambda来解决这个问题。</p>

<pre><code class="language-java">List&lt;Integer&gt; numbers = Arrays.asList(1, 2, 3, 4, 5);
List&lt;Integer&gt; squares = 
                numbers.stream()
                    .map(n -&gt; n * n)
                    .collect(toList());
</code></pre>

<p>(2) 给定两个数字列表，如何返回所有的数对呢？例如，给定列表[1, 2, 3]和列表[3, 4]，应该返回[(1, 3), (1, 4), (2, 3), (2, 4), (3, 3), (3, 4)]。为简单起见，你可以用有两个元素的数组来代表数对。</p>

<p>答案：你可以使用两个 map 来迭代这两个列表，并生成数对。但这样会返回一个 <code>Stream-&lt;Stream&lt;Integer[]&gt;&gt;</code> 。你需要让生成的流<strong>扁平化</strong>，以得到一个 <code>Stream&lt;Integer[]&gt;</code> 。这正是 flatMap 所做的：</p>

<pre><code class="language-java">List&lt;Integer&gt; numbers1 = Arrays.asList(1, 2, 3);
List&lt;Integer&gt; numbers2 = Arrays.asList(3, 4);
List&lt;int[]&gt; pairs =
            numbers1.stream()
                .flatMap(i -&gt; numbers2.stream()
                                    .map(j -&gt; new int[]{i, j})
                )
                .collect(toList());
</code></pre>

<p>(3) 如何扩展前一个例子，只返回总和能被3整除的数对呢？例如(2, 4)和(3, 3)是可以的。</p>

<p>答案：你在前面看到了， filter 可以配合谓词使用来筛选流中的元素。因为在 flatMap操作后，你有了一个代表数对的 int[] 流，所以你只需要一个谓词来检查总和是否能被3整除就可以了：</p>

<pre><code class="language-java">List&lt;Integer&gt; numbers1 = Arrays.asList(1, 2, 3);
List&lt;Integer&gt; numbers2 = Arrays.asList(3, 4);
List&lt;int[]&gt; pairs =
                numbers1.stream()
                    .flatMap(i -&gt; numbers2.stream()
                                    .filter(j -&gt; (i + j) % 3 == 0)
                                    .map(j -&gt; new int[]{i, j})
                    )
                    .collect(toList());
</code></pre>

<p>其结果是[(2, 4), (3, 3)]。</p>

<h1 id="查找与匹配">查找与匹配</h1>

<p>另一个常见的数据处理套路是看看数据集中的某些元素是否匹配一个给定的属性。Stream API通过 allMatch 、 anyMatch 、 noneMatch 、 findFirst 和 findAny 方法提供了这样的工具。</p>

<p>Java 8的库设计人员引入了 Optional<T> ，这样就不用返回众所周知容易出问题的 null 了。</p>

<h1 id="归约">归约</h1>

<p>到目前为止，你见到过的终端操作都是返回一个 boolean （ allMatch 之类的）、 void（ forEach ）或 Optional 对象（ findAny 等）。你也见过了使用 collect 来将流中的所有元素组合成一个 List 。</p>

<p>还有一类查询需要将流中所有元素反复结合起来，得到一个值，比如一个 Integer 。这样的查询可以被归类为归约操作（将流归约成一个值）。用函数式编程语言的术语来说，这称为折叠（fold），因为你可以将这个操作看成把一张长长的纸（你的流）反复折叠成一个小方块，而这就是折叠操作的结果。</p>

<p>reduce 操作是如何作用于一个流的：Lambda反复结合每个元素，直到流被归约成一个值。</p>

<pre><code class="language-java">
  T reduce(T identity, BinaryOperator&lt;T&gt; accumulator);

  // 等价于 

  T result = identity;
  for (T element : this stream)
      result = accumulator.apply(result, element)
  return result;

</code></pre>

<p>查找最值：</p>

<p><code>*.reduce(Integer::max)</code> or <code>*.reduce(Integer::min)</code>, 得到 Optional 。</p>

<p>计算流中元素的个数：</p>

<p><code>*.map(d -&gt; 1).reduce(0, Integer::sum)</code> 。</p>

<p>map 和 reduce 的连接通常称为 map-reduce 模式，因Google用它来进行网络搜索而出名，因为它很容易并行化。</p>

<h1 id="一个小总结">一个小总结</h1>

<p>中间操作和终端操作</p>

<table>
<thead>
<tr>
<th>操 作</th>
<th>类 型</th>
<th>返回类型</th>
<th>使用的类型/函数式接口</th>
<th>函数描述符</th>
</tr>
</thead>

<tbody>
<tr>
<td>filter</td>
<td>中间</td>
<td><code>Stream&lt;T&gt;</code></td>
<td><code>Predicate&lt;T&gt;</code></td>
<td><code>T -&gt; boolean</code></td>
</tr>

<tr>
<td>distinct</td>
<td>中间 (有状态-无界)</td>
<td><code>Stream&lt;T&gt;</code></td>
<td></td>
<td></td>
</tr>

<tr>
<td>skip</td>
<td>中间 (有状态-有界)</td>
<td><code>Stream&lt;T&gt;</code></td>
<td><code>long</code></td>
<td></td>
</tr>

<tr>
<td>limit</td>
<td>中间 (有状态-有界)</td>
<td><code>Stream&lt;T&gt;</code></td>
<td><code>long</code></td>
<td></td>
</tr>

<tr>
<td>map</td>
<td>中间</td>
<td><code>Stream&lt;R&gt;</code></td>
<td><code>Function&lt;T, R&gt;</code></td>
<td><code>T -&gt; R</code></td>
</tr>

<tr>
<td>flatMap</td>
<td>中间</td>
<td><code>Stream&lt;R&gt;</code></td>
<td><code>Function&lt;T, Stream&lt;R&gt;&gt;</code></td>
<td><code>T -&gt; Stream&lt;R&gt;</code></td>
</tr>

<tr>
<td>sorted</td>
<td>中间 (有状态-无界)</td>
<td><code>Stream&lt;T&gt;</code></td>
<td><code>Comparator&lt;T&gt;</code></td>
<td><code>(T, T) -&gt; int</code></td>
</tr>

<tr>
<td>anyMatch</td>
<td>终端</td>
<td><code>boolean</code></td>
<td><code>Predicate&lt;T&gt;</code></td>
<td><code>T -&gt; boolean</code></td>
</tr>

<tr>
<td>noneMatch</td>
<td>终端</td>
<td><code>boolean</code></td>
<td><code>Predicate&lt;T&gt;</code></td>
<td><code>T -&gt; boolean</code></td>
</tr>

<tr>
<td>allMatch</td>
<td>终端</td>
<td><code>boolean</code></td>
<td><code>Predicate&lt;T&gt;</code></td>
<td><code>T -&gt; boolean</code></td>
</tr>

<tr>
<td>findAny</td>
<td>终端</td>
<td><code>Optional&lt;T&gt;</code></td>
<td></td>
<td></td>
</tr>

<tr>
<td>findFirst</td>
<td>终端</td>
<td><code>Optional&lt;T&gt;</code></td>
<td></td>
<td></td>
</tr>

<tr>
<td>forEach</td>
<td>终端</td>
<td><code>void</code></td>
<td><code>Consumer&lt;T&gt;</code></td>
<td><code>T -&gt; void</code></td>
</tr>

<tr>
<td>collect</td>
<td>终端</td>
<td><code>R</code></td>
<td><code>Collector&lt;T, A, R&gt;</code></td>
<td></td>
</tr>

<tr>
<td>reduce</td>
<td>终端 (有状态-有界)</td>
<td><code>Optional&lt;T&gt;</code></td>
<td><code>BinaryOperator&lt;T&gt;</code></td>
<td><code>(T, T) -&gt; T</code></td>
</tr>

<tr>
<td>count</td>
<td>终端</td>
<td><code>long</code></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>

<h1 id="一个例子">一个例子</h1>

<p>书中摘抄：</p>

<p>现在我们来看一个难一点儿的例子，让你巩固一下有关数值流以及到目前为止学过的所有流
操作的知识。如果你接受这个挑战，任务就是创建一个勾股数流。</p>

<ol>
<li><p>勾股数</p>

<p>那么什么是勾股数（毕达哥拉斯三元数）呢？我们得回到从前。在一堂激动人心的数学课上，
你了解到，古希腊数学家毕达哥拉斯发现了某些三元数 (a, b, c) 满足公式 a * a + b * b =
c * c ，其中 a 、 b 、 c 都是整数。例如，(3, 4, 5)就是一组有效的勾股数，因为3 * 3 + 4 * 4 = 5 * 5
或9 + 16 = 25。这样的三元数有无限组。例如，(5, 12, 13)、(6, 8, 10)和(7, 24, 25)都是有效的勾股
数。勾股数很有用，因为它们描述的正好是直角三角形的三条边长.</p></li>

<li><p>表示三元数</p>

<p>那么，怎么入手呢？第一步是定义一个三元数。虽然更恰当的做法是定义一个新的类来表示
三元数，但这里你可以使用具有三个元素的 int 数组，比如 new int[]{3, 4, 5} ，来表示勾股
数(3, 4, 5)。现在你就可以用数组索引访问每个元素了。</p></li>

<li><p>筛选成立的组合</p>

<p>假定有人为你提供了三元数中的前两个数字： a 和 b 。怎么知道它是否能形成一组勾股数呢？
你需要测试 <code>a * a + b * b</code> 的平方根是不是整数，也就是说它没有小数部分——在Java里可以
使用 <code>expr % 1</code> 表示。如果它不是整数，那就是说 c 不是整数。你可以用 filter 操作表达这个要
求（你稍后会了解到如何将其连接起来成为有效代码）：
<code>filter(b -&gt; Math.sqrt(a*a + b*b) % 1 == 0)</code>
假设周围的代码给 a 提供了一个值，并且 stream 提供了 b 可能出现的值， filter 将只选出那
些可以与 a 组成勾股数的 b 。你可能在想 Math.sqrt(a * a + b * b) % 1 == 0 这一行是怎么
回事。简单来说，这是一种测试 Math.sqrt(a * a + b * b) 返回的结果是不是整数的方法。
如果平方根的结果带了小数，如9.1，这个条件就不成立（9.0是可以的）。</p></li>

<li><p>生成三元组</p>

<p>在筛选之后，你知道 a 和 b 能够组成一个正确的组合。现在需要创建一个三元组。你可以使用
map 操作，像下面这样把每个元素转换成一个勾股数组：</p>

<pre><code class="language-java">stream.filter(b -&gt; Math.sqrt(a*a + b*b) % 1 == 0)
        .map(b -&gt; new int[]{a, b, (int) Math.sqrt(a * a + b * b)});
</code></pre></li>

<li><p>生成 b 值</p>

<p>胜利在望！现在你需要生成 b 的值。前面已经看到， <code>Stream.rangeClosed</code> 让你可以在给定
区间内生成一个数值流。你可以用它来给 b 提供数值，这里是1到100：</p>

<pre><code class="language-java">IntStream.rangeClosed(1, 100)
    .filter(b -&gt; Math.sqrt(a*a + b*b) % 1 == 0)
    .boxed()
    .map(b -&gt; new int[]{a, b, (int) Math.sqrt(a * a + b * b)});
        
</code></pre>

<p>请注意，你在 filter 之后调用 boxed ，从 rangeClosed 返回的 IntStream 生成一个
Stream<Integer> 。这是因为你的 map 会为流中的每个元素返回一个 int 数组。而 IntStream
中的 map 方法只能为流中的每个元素返回另一个 int ，这可不是你想要的！你可以用 IntStream
的 mapToObj 方法改写它，这个方法会返回一个对象值流：</p>

<pre><code class="language-java">IntStream.rangeClosed(1, 100)
    .filter(b -&gt; Math.sqrt(a*a + b*b) % 1 == 0)
    .mapToObj(b -&gt; new int[]{a, b, (int) Math.sqrt(a * a + b * b)});
</code></pre></li>

<li><p>生成值</p>

<p>这里有一个关键的假设：给出了 a 的值。 现在，只要已知 a 的值，你就有了一个可以生成勾
股数的流。如何解决这个问题呢？就像 b 一样，你需要为 a 生成数值！最终的解决方案如下所示：</p>

<pre><code class="language-java">Stream&lt;int[]&gt; pythagoreanTriples =
    IntStream.rangeClosed(1, 100).boxed()
            .flatMap(a -&gt; 
                IntStream.rangeClosed(a, 100)
                    .filter(b -&gt; Math.sqrt(a*a + b*b) % 1 == 0)
                    .mapToObj(b -&gt; new int[]{a, b, (int)Math.sqrt(a * a + b * b)})
            );
</code></pre>

<p>好的， flatMap 又是怎么回事呢？首先，创建一个从1到100的数值范围来生成 a 的值。对每
个给定的 a 值，创建一个三元数流。要是把 a 的值映射到三元数流的话，就会得到一个由流构成的
流。 flatMap 方法在做映射的同时，还会把所有生成的三元数流扁平化成一个流。这样你就得到
了一个三元数流。还要注意，我们把 b 的范围改成了 a 到100。没有必要再从1开始了，否则就会造
成重复的三元数，例如(3,4,5)和(4,3,5)。</p></li>

<li><p>运行代码</p>

<p>现在你可以运行解决方案，并且可以利用我们前面看到的 limit 命令，明确限定从生成的流
中要返回多少组勾股数了：</p>

<pre><code class="language-java">pythagoreanTriples.limit(5)
    .forEach(t -&gt; System.out.println(t[0] + &quot;, &quot; + t[1] + &quot;, &quot; + t[2]));
</code></pre></li>

<li><p>你还能做得更好吗？</p>

<p>目前的解决办法并不是最优的，因为你要求两次平方根。让代码更为紧凑的一种可能的方法
是，先生成所有的三元数 (a*a, b*b, a*a+b*b) ，然后再筛选符合条件的：</p>

<pre><code class="language-java">
Stream&lt;double[]&gt; pythagoreanTriples2 =
        IntStream.rangeClosed(1, 100).boxed()
            .flatMap(a -&gt;
                IntStream.rangeClosed(a, 100)
                    .mapToObj(b -&gt; new double[]{a, b, Math.sqrt(a*a + b*b)})
                .filter(t -&gt; t[2] % 1 == 0));

</code></pre></li>
</ol>

<h1 id="构建流">构建流</h1>

<ol>
<li><p>值创建</p>

<p>使用静态方法 <code>Stream.of()</code>.</p>

<pre><code class="language-java">@SafeVarargs                                                            
@SuppressWarnings(&quot;varargs&quot;) // Creating a stream from an array is safe 
public static&lt;T&gt; Stream&lt;T&gt; of(T... values) {                            
    return Arrays.stream(values);                                       
}                                                                       
</code></pre></li>

<li><p>由数组创建</p>

<p>使用静态方法<code>Arrays.stream()</code>.</p>

<pre><code class="language-java">public static &lt;T&gt; Stream&lt;T&gt; stream(T[] array) {                            
    return stream(array, 0, array.length);                                 
}                                                                          
</code></pre></li>

<li><p>由文件生成流</p>

<p>文件io一般都有生成流的静态方法。</p>

<pre><code class="language-java"> public static Stream&lt;String&gt; lines(Path path, Charset cs) throws IOException {}
</code></pre></li>

<li><p>由函数生成</p>

<pre><code class="language-java">public static&lt;T&gt; Stream&lt;T&gt; iterate(final T seed, final UnaryOperator&lt;T&gt; f) {
    Objects.requireNonNull(f);                                              
    final Iterator&lt;T&gt; iterator = new Iterator&lt;T&gt;() {                        
        @SuppressWarnings(&quot;unchecked&quot;)                                      
        T t = (T) Streams.NONE;                                             
                                                                                
        @Override                                                           
        public boolean hasNext() {                                          
            return true;                                                    
        }                                                                   
                                                                                
        @Override                                                           
        public T next() {                                                   
            return t = (t == Streams.NONE) ? seed : f.apply(t);             
        }                                                                   
    };                                                                      
    return StreamSupport.stream(Spliterators.spliteratorUnknownSize(        
            iterator,                                                       
            Spliterator.ORDERED | Spliterator.IMMUTABLE), false);           
}                                                                           
</code></pre>

<pre><code class="language-java">public static&lt;T&gt; Stream&lt;T&gt; generate(Supplier&lt;T&gt; s) {                                               
    Objects.requireNonNull(s);                                                                     
    return StreamSupport.stream(                                                                   
            new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef&lt;&gt;(Long.MAX_VALUE, s), false);
}                                                                                                  
                                                                                                    
</code></pre></li>
</ol>

<h1 id="官方总结">官方总结</h1>

<p>这一章很长，但是很有收获！现在你可以更高效地处理集合了。事实上，流让你可以简洁地表达复杂的数据处理查询。此外，流可以透明地并行化。以下是你应从本章中学到的关键概念。</p>

<ul>
<li>Streams API可以表达复杂的数据处理查询。</li>
<li>你可以使用 filter 、 distinct 、 skip 和 limit 对流做筛选和切片。</li>
<li>你可以使用 map 和 flatMap 提取或转换流中的元素。</li>
<li>你可以使用 findFirst 和 findAny 方法查找流中的元素。你可以用 allMatch 、noneMatch 和 anyMatch 方法让流匹配给定的谓词。</li>
<li>这些方法都利用了短路：找到结果就立即停止计算；没有必要处理整个流。</li>
<li>你可以利用 reduce 方法将流中所有的元素迭代合并成一个结果，例如求和或查找最大元素。</li>
<li>filter 和 map 等操作是无状态的，它们并不存储任何状态。 reduce 等操作要存储状态才能计算出一个值。 sorted 和 distinct 等操作也要存储状态，因为它们需要把流中的所有元素缓存起来才能返回一个新的流。这种操作称为有状态操作。</li>
<li>流有三种基本的原始类型特化： IntStream 、 DoubleStream 和 LongStream 。它们的操作也有相应的特化。</li>
<li>流不仅可以从集合创建，也可从值、数组、文件以及 iterate 与 generate 等特定方法创建。</li>
<li>无限流是没有固定大小的流。</li>
</ul>
                        </div>

                        


                        

<div class="post-archive">
    <h2>See Also</h2>
    <ul class="listing">
        
        <li><a href="/hugo/post/java/java_8_inaction/ch04/">Ch04 引入流</a></li>
        
        <li><a href="/hugo/post/java/java_8_inaction/ch03/">Ch03 Lambda表达式</a></li>
        
        <li><a href="/hugo/post/java/java_8_inaction/ch02/">Ch02 通过行为参数化传递代码</a></li>
        
        <li><a href="/hugo/post/java/java_8_inaction/ch01/">Ch01 为什么要关心Java8</a></li>
        
    </ul>
</div>


                        <div class="post-meta meta-tags">
                            
                            <ul class="clearfix">
                                
                                <li><a href="https://otswang.gitee.io/hugo/tags/java">java</a></li>
                                
                            </ul>
                            
                        </div>
                    </article>
                    
    

    
    
                </div>
            </div>
            <div id="secondary">

    <section class="widget">
        <form id="search" action="//www.google.com/search" method="get" accept-charset="utf-8" target="_blank" _lpchecked="1">
      
      <input type="text" name="q" maxlength="20" placeholder="Search">
      <input type="hidden" name="sitesearch" value="https://otswang.gitee.io/hugo/">
      <button type="submit" class="submit icon-search"></button>
</form>
    </section>

    

    <section class="widget">
        <h3 class="widget-title">最近文章</h3>
<ul class="widget-list">
    
    <li>
        <a href="https://otswang.gitee.io/hugo/post/middle_tools/kafka/" title="Kafka">Kafka</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/post/middle_tools/zk_starter/" title="Zookeeper Starter">Zookeeper Starter</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/post/java/javafx_starter/" title="Java Fx 入门学习">Java Fx 入门学习</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/post/other/codec/" title="常用加密/签名算法">常用加密/签名算法</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/post/other/docker/" title="Docker学习">Docker学习</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/post/webapp/react_starter/" title="React 从入门到入门">React 从入门到入门</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/post/lang/csharpe_starter/" title="C#语言从入门到入门">C#语言从入门到入门</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/post/java/java8time/" title="Java checklist">Java checklist</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/post/other/keyboard/" title="快捷键记录">快捷键记录</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/post/other/vim/" title="vim 使用记录">vim 使用记录</a>
    </li>
    
</ul>
    </section>

    

    <section class="widget">
        <h3 class="widget-title">分类</h3>
<ul class="widget-list">
    
    <li>
        <a href="https://otswang.gitee.io/hugo/categories/algorithm/">algorithm(4)</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/categories/design/">design(7)</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/categories/doc/">doc(2)</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/categories/go/">go(3)</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/categories/java/">java(7)</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/categories/java-java8inaction/">java-java8inaction(11)</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/categories/java-juc/">java-juc(3)</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/categories/java-jvm/">java-jvm(2)</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/categories/java-spring/">java-spring(2)</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/categories/java-thinkinginjava/">java-thinkinginjava(11)</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/categories/middletools/">middletools(9)</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/categories/other/">other(24)</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/categories/qt/">qt(2)</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/categories/smallquickappweb/">smallquickappweb(4)</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/categories/webapp/">webapp(1)</a>
    </li>
    
    <li>
        <a href="https://otswang.gitee.io/hugo/categories/%E5%B0%8F%E4%BC%97%E8%AF%AD%E8%A8%80%E4%B8%80%E7%9E%A5/">小众语言一瞥(4)</a>
    </li>
    
</ul>
    </section>

    <section class="widget">
        <h3 class="widget-title">标签</h3>
<div class="tagcloud">
    
    <a href="https://otswang.gitee.io/hugo/tags/c/">c#</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/checklist/">checklist</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/codec/">codec</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/docker/">docker</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/elk/">elk</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/emmet/">emmet</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/file/">file</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/freemarker/">freemarker</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/git/">git</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/go/">go</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/http/">http</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/interview/">interview</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/java/">java</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/javascript/">javascript</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/juc/">juc</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/jvm/">jvm</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/keyboard/">keyboard</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/linux/">linux</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/lua/">lua</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/manjaro/">manjaro</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/map/">map</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/markdown/">markdown</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/mq/">mq</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/mybatis/">mybatis</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/mycat/">mycat</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/mysql/">mysql</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/nginx/">nginx</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/qt/">qt</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/react/">react</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/redis/">redis</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/rust/">rust</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/spring/">spring</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/springboot/">springboot</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/stream/">stream</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/tcp/">tcp</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/thymeleaf/">thymeleaf</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/tree/">tree</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/uml/">uml</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/vim/">vim</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/vlang/">vlang</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/vue/">vue</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/zookeeper/">zookeeper</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/%E4%BD%8D%E8%BF%90%E7%AE%97/">位运算</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/%E5%8D%8F%E8%AE%AE/">协议</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/">多线程</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/%E5%B9%B6%E5%8F%91%E9%9B%86%E5%90%88/">并发集合</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/">微信小程序</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/%E5%BF%AB%E5%BA%94%E7%94%A8/">快应用</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/%E6%97%B6%E9%97%B4%E7%B1%BB/">时间类</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/%E6%A8%A1%E6%9D%BF%E5%BC%95%E6%93%8E/">模板引擎</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/%E7%AE%97%E6%B3%95/">算法</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/%E7%BC%96%E7%A8%8B/">编程</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/%E7%BC%96%E7%A8%8B%E8%A7%84%E8%8C%83/">编程规范</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/%E8%AE%BE%E8%AE%A1/">设计</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/">设计模式</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/%E9%99%90%E6%B5%81/">限流</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1/">面向对象</a>
    
    <a href="https://otswang.gitee.io/hugo/tags/%E9%9D%A2%E8%AF%95/">面试</a>
    
</div>
    </section>

    

    <section class="widget">
        <h3 class="widget-title">其它</h3>
        <ul class="widget-list">
            <li><a href="https://otswang.gitee.io/hugo/index.xml">文章 RSS</a></li>
        </ul>
    </section>

    

</div>
        </div>
    </div>
</div>
<footer id="footer">
    <div class="container">
        &copy; 2020 <a href="https://otswang.gitee.io/hugo/">The Sky of OtsWang By OtsWang</a>.
        Powered by <a rel="nofollow noreferer noopener" href="https://gohugo.io" target="_blank">Hugo</a>.
        <a href="https://www.flysnow.org/" target="_blank">Theme</a> based on <a href="https://github.com/Dudiao137/maupassant-hugo" target="_blank">maupassant-ots</a>.
        
    </div>
</footer>


    <script type="text/javascript">
    
    (function(){
        $("pre code").parent().addClass("line-numbers")
    }())

    window.MathJax = {
        tex2jax: {
            inlineMath: [ ['$','$'] ],
            processEscapes: true
        }
    };
    </script>
    <script type="text/javascript" src="/hugo/js/prism.js" async="true"></script>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML' async></script>

<a id="rocket" href="#top"></a>
<script type="text/javascript" src="/hugo/js/totop.js?v=0.0.0" async=""></script>







 
 <script src="https://mermaidjs.github.io/scripts/mermaid.min.js"></script>
 <script>
       mermaid.initialize({ startOnLoad: true });
 </script>
</body>
</html>
